<!DOCTYPE html>
<html lang="ch">
<head>
    <meta charset="UTF-8">
    <title>Es6基础</title>
</head>
<body>
es6默认严格模式
<!-- browser.js 是换各种ES*代码为浏览器可识别的ES代码 此处是为了将es6转成es5
     https://www.babeljs.cn/   也可以用其他转换器
     text/babel 告诉浏览器这不是js代码，因为浏览器会默认js解析 -->
<!--<script src="browser.js"></script>-->
<script type="text/babel">
    // let show = () =>{
    //     return "aaa";
    // };
    // let show1 = ()=> "bbb";
    // alert(show());
    // alert(show1());
</script>

<script type="text/javascript">
/** let 声明变量 **/
// 只在let所在的代码块内有效、不存在变量提升
//     {
//         let a = 12 // 块作用域,只在这个代码块生效 使用var就没事,他是函数作用域
//     }
//     console.log(a) // 报错
// 暂时性死区（temporal dead zone，简称 TDZ）
//     var tmp = 123;
//     if (true) {
//         tmp = 'abc'; // 报错 块级作用域内let 绑定这个块级作用域，所以在let声明变量前，对tmp赋值会报错
//         typeof tmp; // 也会报错 ReferenceError
//         let tmp;
//     }
//     function bar(x = y, y = 2) { // x等于y,而此时y还没有声明,属于“死区”
//         let a = a;  // 也会报错 在a的声明语句还没有执行完成前,就去取a的值,导致报错a未定义
//         return [x, y];
//     }
//     bar(); // 报错
// 不允许重复声明
//     function func(arg) { // 函数圆括号里和函数体是同作用域 局部变量的声明是优先于参数的赋值
//         let arg;
//     }
//     func() // 报错
//     function func(arg) { // 循环圆括号里和循环体是不同的作用域
//         {
//             let arg;
//         }
//     }
//     func() // 不报错
// 块级作用域与函数声明
//  ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明
//     // 情况一  本来会报错，但浏览器为了兼容老代码就还是支持在块级作用域里声明函数
//     if (true) {
//         function f() {}
//     }
//     // 情况二
//     try {
//         function f() {}
//     } catch(e) {
//         // ...
//     }
//  ES6允许在块级作用域之中声明函数 块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用
//     function f() { console.log('a'); }
//     (function () {
//         if(false) {
//             // 重复声明一次函数f
//             function f() { console.log('b'); } // 在es5中,函数定义会提升到头部,导致下面的为b
//         }
//         f(); // es6中理论为a,因为函数定义只在if块生效,但实际中会报错,浏览器有一下行为方式
//             // 1.允许在块级作用域内声明函数。
//             // 2.函数声明类似于var，即会提升到全局作用域或函数作用域的头部。
//             // 3.同时，函数声明还会提升到所在的块级作用域的头部。
//             // 这样就导致,在(function(){ ***}中,就定义了f=undefined,把全局的f覆盖了,
//             //    变成了undefined,运行到f()时,就报不是函数错误
//     }());
/** const 声明常量 **/
// 常量的值就不能改变 否则报错
// 一旦声明就必须立即初始化否则报错
// 作用域与let命令相
//     本质:const指向的那个内存地址所保存的数据不得改动,对于简单类型的数据(数值|字符串|布尔值),
//         值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),
//         变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的
//         (即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的就完全不能控制了
//            const foo = {};
//            foo.prop = 123;// 为 foo 添加一个属性，可以成功
//            foo = {}; // 将 foo 指向另一个对象，就会报错 TypeError: "foo" is read-only
//            const a = [];
//            a.push('Hello'); // 可执行
//            a.length = 0;    // 可执行
//            a = ['Dave'];    // 报错
//         如果真的想将对象冻结，应该使用Object.freeze方法。
// 顶层对象的属性
//     let|const|class声明的全局变量,不属于顶层对象的属性 es5中var创建的是全局变量
//        let a= 1
//        window.a // undefined
// globalThis
//     任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。
//        浏览器里面，顶层对象是window，但Node和WebWorker没有window。
//        浏览器和 Web Worker 里面，self也指向顶层对象，但是 Node 没有self。
//        Node 里面，顶层对象是global，但其他环境都不支持。
//     因以上原因故有globalThis对象


/** 数组的解构赋值 **/
// ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
//     let [a, b, c] = [1, 2, 3]; // 本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
// 嵌套数组进行解构
//     let [a, [[b], c]] = [1, [[2], 3]]; // a=1,b=2,c=3
//     let [ , , c] = ["1", "2", "3"]; // c=3
//     let [a, , c] = [1, 2, 3]; // a=1 c=3
//     let [a, ...b] = [1, 2, 3, 4]; // a=1 b=[2,3,4]
// 如果解构不成功,变量的值就等于undefined
//     let [a, b, ...c] = [1]; // a=1 b=undefined c=[]
//     let [a] = []; // a=undefined
// 不完全解构 即等号左边的模式,只匹配部分等号右边的数组,但解构依然可以成功
//     let [x, y] = [1, 2, 3]; //x=1 y=2 ,
//     let [a, [b], d] = [1, [2, 3], 4]; // a=1 b=[2] c=4
// 如果等号的右边不是数组(或者严格地说,不是可遍历的结构),将会报错。
//     let [foo] = 1; // 转为对象以后不具备Iterator接口
//     let [foo] = false; // 如上
//     let [foo] = NaN; // 如上
//     let [foo] = undefined; //如上
//     let [foo] = null; // 如上
//     let [foo] = {}; // 本身不具备Iterator接口
// 只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值
//     let [x, y, z] = new Set(['a', 'b', 'c']); // x ='a' Set结构,也可以使用数组的解构赋值
//     function* fibs() { // Generator 函数
//         let a = 0;
//         let b = 1;
//         while (true) {
//             yield a;
//             [a, b] = [b, a + b];
//         }
//     }
//     let [first, second, third, fourth, fifth, sixth] = fibs(); // sixth=5
// 默认值
//     let [foo = true] = []; //foo=true 解构赋值允许指定默认值
//    ES6内部使用严格相等运算符(===),判断一个位置是否有值.所以,只有当一个数组成员严格等于undefined,默认值才会生效
//     let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
//     let [x = 1] = [null]; // x=null 因为null不严格等于undefined
// 如果默认值是一个表达式，那么这个表达式是惰性求值的，即只有在用到的时候，才会求值。
//     function f() {console.log('aaa');}
//     let [x = f()] = [1]; // x=1 因为x能取到值,所以函数f根本不会执行 上面的代码等价于下面的代码。
//     let x;
//     if ([1][0] === undefined) {
//         x = f();
//     } else {
//         x = [1][0];
//     }
// 默认值可以引用解构赋值的其他变量，但该变量必须已经声明
//     let [x = 1, y = x] = []; // x=1; y=1
//     let [x = y, y = 1] = []; // ReferenceError: y is not defined，因为y还没申明

/** 对象的解构赋值 **/
// 变量必须与属性同名，才能取到正确的值,否则返回undefined.对象的解构赋值可以取到继承的属性
//    let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; // bar:bbb foo:aaa
//    let { log, sin, cos } = Math; // 将Math的对数,正弦,余弦三个方法,赋值到对应的变量
//    let { foo: baz,bar } = { foo: 'aaa', bar: 'bbb' }; // foo报错 baz:aaa bar:bbb
//        foo是匹配的模式，baz才是变量。真正被赋值的是变量baz，而不是模式foo,等同于下面
//        let {foo:baz,bar:bar}={foo:'aaa',bar:'bbb'}; // 会自动创建一个同名的变量
//    let { p: [x, { y }] } = {p: ['Hello',{ y: 'World' }]}; // 嵌套结构的对象
//    let { p,p: [x, { y }] } = {p: ['Hello',{ y: 'World' }]}; // 这样p也会变成变量被赋值
//    let {loc,loc:{start},loc:{start:{line}}}={loc:{start:{line:1,column:5}}}
//        // loc:{start: Object} start:{line: 1, column: 5} line:1
// 默认值
//    默认值生效的条件是，对象的属性值严格等于undefined。
//    var {x, y = 5} = {x: 1}; // x=1 y=5
//    var {x = 3,y=5} = {x: undefined,y:null}; // x=3 y=null
// 注意
//    错误的写法
//    let x; // js引擎会把{x}理解为一个代码块,后面的赋值运算就语法错误了
//    {x} = {x: 1}; // SyntaxError: syntax error
//    正确写法
//    ({x} = {x: 1});
//    数组本质是特殊的对象，因此可以对数组进行对象属性的解构 属性名表达式
//    let {0 :first, [arr.length-1] : last} = [1, 2, 3]; // first:1 last:3

/** 字符串的解构赋值 **/
// 解构赋值时,字符串被转换成了一个类似数组的对象
//    const [a, b, c, d, e] = 'hello'; a:h b:e c:l d:l e:o
//    let {length : len} = 'hello';  // len:5

/** 数值和布尔值的解构赋值 **/
// 解构赋值时，如果等号右边是数值和布尔值，则会先转为对象
//     let {toString: s} = 123;
//     s === Number.prototype.toString // true
//     let {toString: s} = true;
//     s === Boolean.prototype.toString // true
//     数值和布尔值的包装对象都有toString属性，因此变量s都能取到值
// undefined和null无法转为对象，所以对它们进行解构赋值，都会报错
//     let { prop: x } = undefined; // TypeError
//     let { prop: y } = null; // TypeError

/** 函数参数的解构赋值 **/
// 函数参数表面是个数组,但在传入参数时,数组参数就被解构成变量x和y.对内部代码块来说,能感受到的就是x和y
//     function add([x, y]){
//         return x + y;
//     }
//     add([1, 2]);
// 默认值
//     function move({x = 0, y = 0} = {}) {
//         return [x, y];
//     }
//     move({x: 3}); // [3, 0]
//     function move({x, y} = { x: 0, y: 0 }) {
//         return [x, y];
//     }
//     move({x: 3}); // [3, undefined]
//     undefined就会触发函数参数的默认值。
//      [1, undefined, 3].map((x = 'yes') => x);// [ 1, 'yes', 3 ]

/** 解构赋值圆括号  **/
// ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号.议只要有可能,就不要在模式中放置圆括号
// 不能使用圆括号的情况
//     1.变量声明语句 全部报错
//         let [(a)] = [1];
//         let {x: (c)} = {};
//         let ({x: c}) = {};
//         let {(x: c)} = {};
//         let {(x): c} = {};
//         let { o: ({ p: p }) } = { o: { p: 2 } };
//     2.函数参数 函数参数也属于变量声明，因此不能带有圆括号
//         function f([(z)]) { return z; }// 报错
//         function f([z,(x)]) { return x; }// 报错
//     3.赋值语句的模式 全部报错
//         ({ p: a }) = { p: 42 };
//         ([a]) = [5];
//         [({ p: a }), { x: c }] = [{}, {}];
// 可以使用圆括号的情况
//     赋值语句的非模式部分
//         [(b)] = [3]; // 正确
//         ({ p: (d) } = {}); // 正确
//         [(parseInt.prop)] = [3]; // 正确
/** 解构赋值的用途 **/
//     1.交换变量的值
//         let x = 1;
//         let y = 2;
//         [x, y] = [y, x];
//     2.从函数返回多个值
//         function example() {
//             return [1, 2, 3];
//             return {foo: 1,bar: 2};
//         }
//         let { foo, bar } = example();
//         let [ foo, bar ] = example();
//     3.函数参数的定义
//         参数是一组有次序的值
//          function f([x, y, z]) { return }
//          f([1, 2, 3]);
//         参数是一组无次序的值
//          function f({x, y, z}) { return }
//          f({z: 3, y: 2, x: 1});
//     4.提取json数据
//         let jsonData = {id: 42,status: "OK",data: [867, 5309]};
//         let { id, status, data: number } = jsonData;// 42,"OK",[867,5309]
//     5.函数参数的默认值 指定参数的默认值，就避免了在函数体内部再写var foo=config.foo||'default foo'
//         jQuery.ajax = function (url, {
//             async = true,
//             beforeSend = function () {},
//             cache = true,
//             complete = function () {},
//             crossDomain = false,
//             global = true,
//             // ... more config
//         } = {}) {
//             // ... do stuff
//         };
//     6.遍历 Map 结构
//         const map = new Map();
//         map.set('first', 'hello');
//         map.set('second', 'world');
//         // 任何部署了 Iterator 接口的对象，都可以用for...of循环遍历
//         for (let [key, value] of map) {
//             console.log(key + " is " + value);
//         }
//         for (let [key] of map) {
//             // 获取键名
//         }
//         for (let [,value] of map) {
//             // 获取键值
//         }
//     7.输入模块的指定方法
//         const { SourceMapConsumer, SourceNode } = require("source-map");

</script>
</body>
</html>